/*
 * Copyright 2012 McEvoy Software Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package io.milton.httpclient;

import io.milton.common.Path;
import com.ettrema.cache.Cache;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.ConflictException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.exceptions.NotFoundException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author mcevoyb
 */
public abstract class Resource {

    private static final Logger log = LoggerFactory.getLogger(Resource.class);

    static Resource fromResponse(Folder parent, PropFindResponse resp, Cache<Folder, List<Resource>> cache) {
        if (resp.isCollection()) {
            return new Folder(parent, resp, cache);
        } else {
            return new io.milton.httpclient.File(parent, resp);
        }
    }

    /**
     * does percentage decoding on a path portion of a url
     *
     * E.g. /foo > /foo /with%20space -> /with space
     *
     * @param href
     */
    public static String decodePath(String href) {
        // For IPv6
        href = href.replace("[", "%5B").replace("]", "%5D");

        // Seems that some client apps send spaces.. maybe..
        href = href.replace(" ", "%20");
        // ok, this is milton's bad. Older versions don't encode curly braces
        href = href.replace("{", "%7B").replace("}", "%7D");
        try {
            if (href.startsWith("/")) {
                URI uri = new URI("http://anything.com" + href);
                return uri.getPath();
            } else {
                URI uri = new URI("http://anything.com/" + href);
                String s = uri.getPath();
                return s.substring(1);
            }
        } catch (URISyntaxException ex) {
            throw new RuntimeException(ex);
        }
    }
    public Folder parent;
    public String name;
    public String displayName;
    private Date modifiedDate;
    private Date createdDate;
    final List<ResourceListener> listeners = new ArrayList<ResourceListener>();
    private String lockOwner;
    private String lockToken;

    public abstract java.io.File downloadTo(java.io.File destFolder, ProgressListener listener) throws FileNotFoundException, IOException, HttpException, Utils.CancelledException, NotAuthorizedException, BadRequestException;
    private static long count = 0;

    public static long getCount() {
        return count;
    }

    public abstract String encodedUrl();

    /**
     * Special constructor for Host
     */
    Resource() {
        this.parent = null;
        this.name = "";
        this.displayName = "";
        this.createdDate = null;
        this.modifiedDate = null;
        count++;
    }

    public Resource(Folder parent, PropFindResponse resp) {
        count++;
        if (parent == null) {
            throw new NullPointerException("parent is null");
        }
        this.parent = parent;
        name = Resource.decodePath(resp.getName());
        displayName = resp.getDisplayName();
        createdDate = resp.getCreatedDate();
        modifiedDate = resp.getModifiedDate();
        if (resp.getLock() != null) {
            lockToken = resp.getLock().getToken();
            lockOwner = resp.getLock().getOwner();
        } else {
            lockToken = null;
            lockOwner = null;
        }
    }

    public Resource(Folder parent, String name, String displayName, String href, Date modifiedDate, Date createdDate) {
        count++;
        if (parent == null) {
            throw new NullPointerException("parent");
        }
        this.parent = parent;
        this.name = name;
        this.displayName = displayName;
        this.modifiedDate = modifiedDate;
        this.createdDate = createdDate;
    }

    public Resource(Folder parent, String name) {
        count++;
        if (parent == null) {
            throw new NullPointerException("parent");
        }
        this.parent = parent;
        this.name = name;
        this.displayName = name;
        this.modifiedDate = null;
        this.createdDate = null;
    }

    @Override
    protected void finalize() throws Throwable {
        count--;
        super.finalize();
    }

    public void addListener(ResourceListener l) {
        listeners.add(l);
    }

    public String post(Map<String, String> params) throws HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        return host().doPost(encodedUrl(), params);
    }

    public void lock(int timeout) throws HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        if (lockToken != null) {
            log.warn("already locked: " + href() + " token: " + lockToken);
        }
        try {
            lockToken = host().doLock(encodedUrl(), timeout);
        } catch (URISyntaxException ex) {
            throw new RuntimeException(ex);
        }
    }

    public int unlock() throws HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        if (lockToken == null) {
            throw new IllegalStateException("Can't unlock, is not currently locked (no lock token) - " + href());
        }
        try {
            return host().doUnLock(encodedUrl(), lockToken);
        } catch (URISyntaxException ex) {
            throw new RuntimeException(ex);
        }
    }

    public void copyTo(Folder folder) throws IOException, HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        copyTo(folder, name);
    }

    public void copyTo(Folder folder, String destName) throws IOException, HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        try {
            host().doCopy(encodedUrl(), folder.encodedUrl() + io.milton.common.Utils.percentEncode(destName));
        } catch (URISyntaxException ex) {
            throw new RuntimeException(ex);
        }
        folder.flush();
    }

    public void rename(String newName) throws IOException, HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        String dest = "";
        if (parent != null) {
            dest = parent.encodedUrl();
        }
        dest = dest + io.milton.common.Utils.percentEncode(newName);
        int res;
        try {
            res = host().doMove(encodedUrl(), dest);
        } catch (URISyntaxException ex) {
            throw new RuntimeException(ex);
        }
        if (res == 201) {
            this.name = newName;
        }
    }

    public void moveTo(Folder folder) throws IOException, HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        moveTo(folder, name);
    }

    public void moveTo(Folder folder, String destName) throws IOException, HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        log.info("Move: " + this.href() + " to " + folder.href());
        int res;
        try {
            res = host().doMove(encodedUrl(), folder.href() + io.milton.common.Utils.percentEncode(destName));
        } catch (URISyntaxException ex) {
            throw new RuntimeException(ex);
        }
        if (res == 201) {
            this.parent.flush();
            folder.flush();
        }
    }

    public void removeListener(ResourceListener l) {
        listeners.remove(l);
    }

    @Override
    public String toString() {
        return href() + "(" + displayName + ")";
    }

    public void delete() throws IOException, HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
        host().doDelete(encodedUrl());
        notifyOnDelete();
    }

    void notifyOnDelete() {
        if (this.parent != null) {
            this.parent.notifyOnChildRemoved(this);
        }
        List<ResourceListener> l2 = new ArrayList<ResourceListener>(listeners);
        for (ResourceListener l : l2) {
            l.onDeleted(this);
        }
    }

    public Host host() {
        Host h = parent.host();
        if (h == null) {
            throw new NullPointerException("no host");
        }
        return h;
    }

    public String encodedName() {
        return io.milton.common.Utils.percentEncode(name);
    }

    /**
     * Returns the UN-encoded url
     *
     * @return
     */
    public String href() {
        if (parent == null) {
            return name;
            //return encodedName();
        } else {
            //return parent.href() + encodedName();
            return parent.href() + name;
        }
    }

    public Path path() {
        if (parent == null) {
            return Path.root;
            //return encodedName();
        } else {
            //return parent.href() + encodedName();
            return parent.path().child(name);
        }
    }

    public Date getModifiedDate() {
        return modifiedDate;
    }

    public Date getCreatedDate() {
        return createdDate;
    }

    public String getLockToken() {
        return lockToken;
    }

    public String getLockOwner() {
        return lockOwner;
    }
}
